package io.geekidea.boot.framework.page;

import io.geekidea.boot.framework.entity.BaseDocument;
import io.geekidea.boot.framework.utils.ObjectTools;
import io.geekidea.boot.framework.utils.StringUtils;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * MongoDB 分页查询工具类
 *
 * @author shusong.liang
 * 参考: https://www.cnblogs.com/woshimrf/p/mongodb-pagenation-performance.html
 */
@Component
public class MongoPageHelper {

    private static final Logger logger = LoggerFactory.getLogger(MongoPageHelper.class);

    private static final int FIRST_PAGE_NUM = 1;

    /**
     * MongoDB 的 id生成规则已经包含了时间信息
     */
    private static final String ID = "_id";

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 分页查询, 直接返回集合类型的结果
     * 注意：此接口不能用于app接口
     *
     * @see MongoPageHelper#page(Query, Class, Function, PageRequest)
     */
    public <T extends BaseDocument> PageResponse<T> pageQuery(Query query,
                                                              Class<T> entityClass, PageRequest pageRequest) {

        //添加返回结果是没有被删除的
        query.addCriteria(Criteria.where("deleted").is(Boolean.FALSE));
        query.with(Sort.by(Sort.Direction.DESC, "createDate"));
        return page(query, entityClass, Function.identity(), pageRequest);
    }

    /**
     * 分页查询, 自定义返回数据的封装
     * 注意：此接口不能用于app接口
     *
     * @see MongoPageHelper#page(Query, Class, Function, PageRequest)
     */
    public <T extends BaseDocument, R> PageResponse<R> pageQuery(Query query, Class<T> entityClass,
                                                                 Function<T, R> mapper, PageRequest pageRequest) {
        return page(query, entityClass, mapper, pageRequest);
    }


    /**
     * 分页查询
     *
     * @param query       Mongo Query对象, 构造你自己的查询条件
     * @param entityClass Mongo collection定义的 entityClass, 用来确定查询哪个集合
     * @param mapper      映射器, 从 db查出来的元素类型是 entityClass,
     *                    如果你想要转换成另一个对象, 比如去掉敏感字段等, 可以使用 mapper来决定如何转换.
     * @param pageRequest 分页查询参数
     * @param <T>         collection定义的 class类型
     * @param <R>         最终返回时, 展现给页面时的一条记录的类型
     * @return PageResult, 一个封装 page信息的对象
     */
    private <T extends BaseDocument, R> PageResponse<R> page(Query query, Class<T> entityClass,
                                                             Function<T, R> mapper, PageRequest pageRequest) {

        PageResponse<R> pageResponse = new PageResponse<>();

        Integer pageNum = pageRequest.getPageNum();
        int pageSize = pageRequest.getPageSize();
        String firstId = pageRequest.getFirstId();
        String lastId = pageRequest.getLastId();
        int direction = pageRequest.getDirection();

        if (pageNum != null && pageNum > 0) {
            pageResponse(query, entityClass, pageResponse, pageNum, pageSize);
        } else {
            Criteria criteria = buildCriteria(null, firstId, lastId, direction);
            query.limit(pageSize);
            //query.with(new Sort(Direction.DESC, ID));
            //如果criteria key为空不能添加，否则报错
            if (criteria.getKey() != null) {
                query.addCriteria(criteria);
            }
        }

        // 执行查询
        List<T> entityList = mongoTemplate.find(query, entityClass);

        pageResponse.setPageSize(pageRequest.getPageSize());
        if (entityList.isEmpty()) {
            pageResponse.setData(new ArrayList<>());
        } else {
            pageResponse.setData(entityList.stream().map(mapper).collect(Collectors.toList()));
        }

        if (!CollectionUtils.isEmpty(entityList)) {
            pageResponse.setFirstId(entityList.get(0).getId());
            pageResponse.setLastId(entityList.get(entityList.size() - 1).getId());
        }

        return pageResponse;
    }


    /**
     * 查询参数拼接
     *
     * @param query
     * @param type  1、精准查询 2、模糊查询 3、时间段范围查询(stratTime, endTime)
     * @return
     * @throws Exception
     */
    private Query queryAccurate(Query query, Map<String, Object> data, Integer type) throws Exception {
        // 分页
//        Pageable pageable = new PageRequest(pageNum, pageSize);
//        query.with(pageable);
//        //创建排序规则
//        Sort sort = new Sort(Sort.Direction.DESC, "cTime");
//        query.with(sort);
        if (data.size() >= 1) {
            throw new RuntimeException("查询参数必须大于1个");
        }

        // 带入
        switch (type) {
            case 1:
                //精确查询
                Iterator iter = data.keySet().iterator();
                while (iter.hasNext()) {
                    String key = (String) iter.next();
                    Object value = data.get(key);
                    //如果name精确查询
                    if (!ObjectTools.isNull(value)) {
                        query.addCriteria(
                            Criteria.where(key).is(value)
                        );
                    }
                }

                break;
            case 2:
                // 模糊查询
                Iterator iterm = data.keySet().iterator();
                while (iterm.hasNext()) {
                    String key = (String) iterm.next();
                    Object value = data.get(key);
                    //如果name使用模糊查询则 创建正则
                    Pattern pattern = Pattern.compile("^.*" + value + ".*$", Pattern.CASE_INSENSITIVE);
                    if (!ObjectTools.isNull(value)) {
                        query.addCriteria(
                            new Criteria().and(key).regex(pattern)
                        );
                    }
                }

                break;
            case 3:
                // 时间段范围查询
//                query.addCriteria(
//                        new Criteria().andOperator(
//                                Criteria.where("cTime").gte(stime),
//                                Criteria.where("cTime").lte(etime)
//                        )
//                );
                Iterator itert = data.keySet().iterator();
                List<Criteria> c = new ArrayList<Criteria>();
                int i = 0;
                while (itert.hasNext()) {
                    String key = (String) itert.next();
                    Object value = data.get(key);
                    i++;
                    if (!ObjectTools.isNull(value)) {
                        if (i < 1){
                          c.add( Criteria.where(key).gte(value));
                        }else{
                            c.add( Criteria.where(key).lte(value));
                        }
                    }
                }
                query.addCriteria(new Criteria().andOperator(c.toArray(new Criteria[c.size()])));
                logger.info("query----",query);
                break;
            default:
                throw new RuntimeException("查询类型不存在");

        }

        return query;
    }

    private <T extends BaseDocument, R> void pageResponse(Query query, Class<T> entityClass,
                                                          PageResponse<R> pageResponse, Integer pageNum, int pageSize) {
        // 页数不为空, 采用 skip-limit的方式分页
        long totalCount = mongoTemplate.count(query, entityClass);
        final int totalPage = (int) Math.ceil(totalCount / (double) pageSize);
        if (pageNum > totalPage) {
            pageNum = FIRST_PAGE_NUM;
        }
        int skip = pageSize * (pageNum - 1);
        query.skip(skip).limit(pageSize);

        pageResponse.setTotalCount(totalCount);
        pageResponse.setTotalPage(totalPage);
        pageResponse.setPageNum(pageNum);
    }

    private Criteria buildCriteria(Criteria criteria, String firstId, String lastId, int direction) {
        if (criteria == null) {
            criteria = new Criteria();
        }
        // 页数为空, 认为无跳页的分页
        if (direction == PageRequest.PageDirection.PREVIOUS) {
            // 向前(APP上滑)
            if (StringUtils.isBlank(firstId)) {
                logger.error("Mongo Page Query PREVIOUS, firstId is empty");

                //throw new BizException("秘钥未托管", ResponseCode.ACCOUNT_NOT_TRUSTEESHIP);
            }
            // id倒序, 向前为 >
            criteria.and(ID).gt(new ObjectId(firstId));

        } else {
            // 向后(APP下滑)
            //两id都为空说明是首次加载
            boolean first = StringUtils.isBlank(firstId) && StringUtils.isBlank(lastId);

            if (!first) {
                //默认向下查指定条数
                if (StringUtils.isBlank(lastId)) {
                    logger.error("Mongo Page Query NEXT, lastId is empty");
                    //throw new BusinessException(BizExceptionEnum.INVALID_PARAMETER);
                } else {
                    // id倒序, 向后为 <
                    //默认向下查指定条数
                    if (!criteria.getCriteriaObject().containsKey(ID)) {
                        criteria.and(ID).lt(new ObjectId(lastId));
                    }
                }
            }

        }
        return criteria;
    }
}
